问题
先来看一个错误的例子:
public class CollectionClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> lst) { return "List"; } public static String classify(Collection<?> c) { return "Unknown Collection"; } public static void main(String[] args) { Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String, String>().values() }; for (Collection<?> c : collections) System.out.println(classify(c)); } }
我们希望打出的是set,list,Unknown Collection。实际上,它的输出是Unknown Collection。这是因为classify方法被重载了,实际上调用哪个重载方法,是在编译时就已经决定了。在这个例子中编译器都认为是Collection<?>类,所以输出的是三个Unknown Collection。因此,在使用重载是应该注意哪些问题?
解决
调用哪个具体的重载方法是在编译时就决定了,根据方法中参数的编译时类型。而对于被覆盖的方法的选择则是动态的,是根据调用该方法的对象的运行时类型,来选择合适的“被覆盖的版本”。覆盖是用来实现多态的,而重载并不是;
使用重载,安全而保守的策略是:永远不要写两个具有相同参数数目的重载方法;
如果一定要重载,那么对于一对重载方法,至少要有一个对应的参数在两个重载方法中的类型“完全不同”。可以看下面这个例子:
public class SetList { public static void main(String[] args) { Set<Integer> set = new TreeSet<Integer>(); List<Integer> list = new ArrayList<Integer>(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set + " " + list); } } //输出结果 [-3, -2, -1] [-2, 0, 2]
Set的输出结果如同我们想的一样,但是List的结果不一样。实际发生的情况是:set.remove(E),选择重载方法的参数实际上是Integer,这里进行了自动装箱,把int装箱成了Integer;对于List,有两个重载函数,这里直接重载了list.remove(i),并没有重载到list.remove(E),是从list的指定位置进行remove,得到结果为-2,0,2。这里最根本的原因在于,由于泛型和自动拆箱和装箱,使得remove(E)和remove(i)这两个方法中的参类型上数并没有“根本的不同”。
结论
能够重载并不意味者应该重载,一般来说,对于多个具有相同参数数目的重载方法,还是尽量避免使用重载。如果不能避免重载,就需要保证每一个重载方法的参数类型无论经过怎样的转换(如泛型和自动拆箱和装箱)后都能“完全不同”,从而根据参数类型能够指向不同的重载方法。